module("engine_conf_creator", package.seeall)
local gcf = require("gray_conf")
local utils = require("gray_utils")

local icheck = utils.icheck

-- 1.0
local VERSION = gcf.VERSION
local VERSION_SUP = gcf.VERSION_SUP
local VERSION_NUM = gcf.VERSION_NUM
local VERSION_MAJOR = gcf.VERSION_MAJOR
local VERSION_MIN= gcf.VERSION_MIN

local gnum = 0

local function serialize(o, f, indent)
    gnum = gnum + 1
    if gnum > 10000 then return nil, "too many lines" end
    if type(o) == 'number' then
        f:write(o)
    elseif type(o) == 'string' then
        f:write(string.format("%q", o))
    elseif type(o) == 'table' then
        f:write("{\n")
        for k, v in pairs(o) do
            f:write(indent, "[")
            local rt, msg = serialize(k, f, indent .. "    ")
            if not rt then return nil, msg end
            f:write("] = ")
            local rt, msg = serialize(v, f, indent .. "    ")
            if not rt then return nil, msg end
            f:write(",\n")
        end
        f:write(indent, "}")
    elseif type(o) == 'boolean' then
        f:write(tostring(o))
    else
        return nil, "can not serialize a "..type(o)
    end
    return true
end


local function parse_time(s)
    local _tmap = {
        y = 60 * 60 * 24 * 365,
        M = 60 * 60 * 24 * 30,
        w = 60 * 60 * 24 * 7,
        d = 60 * 60 * 24,
        h = 60 * 60,
        m = 60,
        s = 1,
    }
    local t = 0
    local max_time = 2145916555
    if s == 'off' then return 0 end
    if s == 'max' then return max_time end
    for n, u in string.gmatch(s, "(%d+)([yMwdhms])") do
        local x = tonumber(n) * assert(_tmap[u])
        t = t + x
    end
    if t == 0 or t > max_time then return nil, "invalid time value" end
    return t
end


local function get_varname_from_name(name)
    if string.match(name, "^cookie_%S+") then
        return name
    elseif string.match(name, "^http_%S+") then
        return name
    elseif string.match(name, "^arg_%S+") then
        return name
    elseif name == "remote_addr" then
        return name
    else
        return "cookie_"..name
    end
end


function create_engine_conf(js, enable)
    local rt, msg
    local engine_conf = {}
    -- just recording it
    if js.cell then
        engine_conf.server_name_var = gcf.cell_upstream_variable
        engine_conf.app_name = js.APP
        engine_conf.cell_local_ups = js.cell_local_ups
    else
        engine_conf.server_name_var = gcf.gray_server_var_name
    end
    -- version
    engine_conf.version = VERSION
    -- sid
    if not tonumber(js.sid) then
        return nil, "no strategy id or bad format"
    end
    engine_conf.cell = js.cell
    engine_conf.sid = js.sid
    engine_conf.enable = enable
    -- ab cookie
    if js.abck ~= nil then
        if not icheck(js.abck, 'table') then return nil, "wrong format of ab-cookie" end 
        local abck = {a = "0", b = "1", o = "-", name = "ab", path = "/",
                      index = 0, expire = "3d" }
        abck.a = js.abck.a or abck.a
        abck.b = js.abck.b or abck.b
        abck.o = js.abck.o or abck.o
        abck.name = js.abck.name or abck.name
        abck.path = js.abck.path or abck.path
        if js.abck.index and not tonumber(js.abck.index) then
            return nil, "invalid ab cookie index: " .. js.abck.index
        end
        abck.index = tonumber(js.abck.index) or abck.index
        if abck.index < 0 or abck.index > 1024 then
            return nil, "invalid ab cookie index: " .. abck.index
        end
        abck.expire, msg = parse_time(js.abck.expire or abck.expire)
        if not abck.expire then return nil, "wrong format in abcookie: "..msg end
        if #abck.a ~= 1 or #abck.b ~= 1 or #abck.o ~= 1 then
            return nil, "wrong format in ab-cookie"
        end
        abck.domain = js.abck.domain
        engine_conf.abck = abck
    end
    -- b servers
    local fcm_max = 3
    local mod_cond_map = {}
    mod_cond_map["=="] = true
    mod_cond_map["<="] = true
    mod_cond_map[">="] = true
    mod_cond_map["<"] = true
    mod_cond_map[">"] = true
    if type(js.bs) ~= 'table' or #js.bs <= 0 then return nil, "no b servers" end
    local bs = {} 
    for _, b in ipairs(js.bs) do
        local seq = tonumber(b.seq)
        bs[seq] = {}
        -- flow control method
        if type(b.fcm) ~= 'table' or #b.fcm <= 0 then
            return nil, "no flow control methods in bserver "..seq
        end
        if #b.fcm > fcm_max then
            return nil, "too many flow control methods in bserver "..seq
        end
        local fcm = {}
        for _, m in ipairs(b.fcm) do
            if type(m.name) ~= 'string' then      
                return nil, "no flow control method name in bserver "..seq
            end
            if fcm[m.name] then
                return nil, "duplicate flow control method in bserver "..seq
            end
            local mc
            if m.name == "mod" then
                mc = {max = 2, name = "cookie__mod_", cond = "==", value = 0}
                if m.conf then
                    if not icheck(m.conf, 'table') then
                        return nil, "wrong format of mod-cookie"
                    end 
                    mc.name = get_varname_from_name(m.conf.name or mc.name)
                    if m.conf.max and not tonumber(m.conf.max) then
                        return nil, "invalid max value in mod conf: "..m.conf.max
                    end
                    if m.conf.value and not tonumber(m.conf.value) then
                        return nil, "invalid value in mod conf: "..m.conf.value
                    end
                    mc.max = tonumber(m.conf.max) or mc.max
                    mc.value = tonumber(m.conf.value) or mc.value
                    mc.cond = m.conf.cond or mc.cond
                    if mc.max <= 0 then
                        return nil, "invalid max value in mod conf: "..mc.max
                    end
                    if mc.value < 0 or mc.value >= mc.max then
                        return nil, "invalid value in mod conf: "..mc.value
                    end
                    if not mod_cond_map[mc.cond] then
                        return nil, "invalid value of mod cond: "..mc.cond
                    end
                end
            elseif m.name == "nick" then
                mc = {name = "cookie__nk_"}
                if m.conf then
                    if not icheck(m.conf, 'table') then
                        return nil, "wrong format of nick-cookie"
                    end 
                    mc.name = get_varname_from_name(m.conf.name or mc.name)
                end
                engine_conf.tair_group = gcf.get_etair_name(js.sid)
                engine_conf.tair_area = tonumber(js.tair.tns)
            elseif m.name == "ip" then
                mc = {}
                if m.conf then
                    if type(m.conf) ~= 'table' then
                        return nil, "wrong format of ip conf"
                    end 
                    if m.conf.ips or m.conf.geo then
                        mc.name = gcf.get_geo_block_name(js.sid, seq)
                        mc.value = "1"
                    end
                end
            else
                return nil, "unknown flow control method in bserver " .. seq
            end
            fcm[m.name] = mc
        end
        bs[seq].fcm = fcm
        -- gray_$sid_$seq_bs 
        bs[seq].sn = gcf.get_server_name(js.sid, seq)
    end
    engine_conf.bs = bs
    -- string.format("gray_%s_a", js.sid)
    engine_conf.asn = gcf.get_server_name(js.sid)
    engine_conf.hosts = js.hosts
    return engine_conf
end


--[[function engine_enable_to_file(engine_conf)
    local f, msg = io.open(gcf.get_engine_enable_fullname(engine_conf.sid), "w")
    if not f then
        return nil, "create gray engine enable file failed: "..(msg or "")
    end
    f:write("return ", tostring(engine_conf.enable_stub))
    f:close()
    return true
end]]--


function engine_conf_to_file(engine_conf)
    local f, msg = io.open(gcf.get_engine_conf_fullname(engine_conf.sid), "w")
    if not f then
        return nil, "create gray engine configure file failed: "..(msg or "")
    end
    -- module line
    f:write(string.format("module(\"%s\", package.seeall)\n",
                          gcf.get_engine_conf_module_name(engine_conf.sid)))
    local real_conf = {conf = engine_conf}
    -- engine configure
    gnum = 0
    for k, v in pairs(real_conf) do
        gnum = gnum + 1
        if gnum > 10000 then return nil, "too many lines" end
        if type(k) == 'string' then
            f:write(string.format("%s", k))
            f:write(" = ")
            local rt, msg = serialize(v, f, "    ")
            if not rt then
                f:close()
                return nil, "create gray engine configure file failed:" .. msg
            end
            f:write("\n")
        end
    end
    f:write("return conf")
    f:close()
    -- require engine enable file
    --[[f:write(string.format("enable = require('%s')\n",
                          gcf.get_engine_enable_module_name(engine_conf.sid)))
    f:close()
    -- enable file
    local rt, msg = engine_enable_to_file(engine_conf)
    if not rt then return nil, msg end]]--
    return true
end


--[[function engine_script_to_file(engine_conf)
    -- engine script file
    local f, msg = io.open(gcf.get_engine_script_fullname(engine_conf.sid), "w")
    if not f then
        return nil, "create engine script file failed: "..(msg or "")
    end
    local fd, msg = io.open(gcf.engine_script_template_fullname, "r")
    if not fd then
        return nil, "create engine script from template failed: " .. msg
    end
    local content = string.gsub(fd:read("*all"), "SID", engine_conf.sid)
    f:write(content)
    fd:close()
    f:close()
    -- gray tair
    if engine_conf.tair_group then
        f, msg = io.open(gcf.get_gray_tair_script_fullname(engine_conf.sid), "w")
        if not f then
            return nil, "create engine script file failed: "..(msg or "")
        end
        fd, msg = io.open(gcf.gray_tair_template_fullname, "r")
        if not fd then
            return nil, "create engine script from template failed: " .. msg
        end
        content = string.gsub(fd:read("*all"), "SID", engine_conf.sid)
        f:write(content)
        fd:close()
        f:close()
    end
    return true
end]]--

getmetatable(engine_conf_creator).__newindex = function (table, key, val)
    error('Attempt to write to undeclared variable "' .. key .. '": '
          .. debug.traceback())
end
